package org.shean.app.rssreader.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.shean.app.rssreader.provider.SzRSSReader;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import android.util.Xml;

public class ChannelSource extends DefaultHandler {
	private static final String TAG = "ChannelSource";
	
	private ContentResolver mContent;
	private static boolean isInitGBKTable = false;
	private long mID;
	private String mRSSURL;

	private boolean isNewChannel = false;
	private String mLastPostTime;
	private String mCurrentMaxTime;
	private ChannelPost mPostBuf;
	
	private GBKReader mGBKReader = new GBKReader();
	
	private int mState;
	
	private static final int STATE_IN_ITEM = (1 << 2);
	private static final int STATE_IN_ITEM_TITLE = (1 << 3);
	private static final int STATE_IN_ITEM_LINK = (1 << 4);
	private static final int STATE_IN_ITEM_DESC = (1 << 5);
	private static final int STATE_IN_ITEM_DATE = (1 << 6);
	private static final int STATE_IN_ITEM_AUTHOR = (1 << 7);
	private static final int STATE_IN_TITLE = (1 << 8);
	
	private static HashMap<String, Integer> mStateMap;

	static {
		mStateMap = new HashMap<String, Integer>();
		mStateMap.put("item", new Integer(STATE_IN_ITEM));
		mStateMap.put("entry", new Integer(STATE_IN_ITEM));
		mStateMap.put("title", new Integer(STATE_IN_ITEM_TITLE));
		mStateMap.put("link", new Integer(STATE_IN_ITEM_LINK));
		mStateMap.put("description", new Integer(STATE_IN_ITEM_DESC));
		mStateMap.put("content", new Integer(STATE_IN_ITEM_DESC));
		mStateMap.put("content:encoded", new Integer(STATE_IN_ITEM_DESC));
		mStateMap.put("dc:date", new Integer(STATE_IN_ITEM_DATE));
		mStateMap.put("updated", new Integer(STATE_IN_ITEM_DATE));
		mStateMap.put("pubDate", new Integer(STATE_IN_ITEM_DATE));
		mStateMap.put("pubdate", new Integer(STATE_IN_ITEM_DATE));
		mStateMap.put("dc:author", new Integer(STATE_IN_ITEM_AUTHOR));
		mStateMap.put("author", new Integer(STATE_IN_ITEM_AUTHOR));
	}
	


	public ChannelSource(Context context) {
		super();
		mContent = context.getContentResolver();

		if (!isInitGBKTable) {
			try {
				InputStream is = context.getAssets().open("gbk.unicode.txt");
				int size = is.available();
				byte[] buffer = new byte[size];
				is.read(buffer);
				is.close();
				GBKTable.init(buffer);
			} catch (IOException e) {

				System.out.println("GBKTable is not included in this build");
			}
			isInitGBKTable = true;
		}
	}

	public long syncDB(long id, String rssurl) throws Exception {
		mID = id;
		mRSSURL = rssurl;

		if (mID == -1) {
			isNewChannel = true;
			mCurrentMaxTime = null;
		} else {
			isNewChannel = false;
			Cursor c = mContent.query(Uri.withAppendedPath(
					SzRSSReader.Channels.CONTENT_URI, Long.toString(mID)),
					new String[] { SzRSSReader.Channels.LAST_BUILD_DATE },
					null, null, null);
			c.moveToFirst();
			mLastPostTime = c
					.getString(c
							.getColumnIndexOrThrow(SzRSSReader.Channels.LAST_BUILD_DATE));
			mCurrentMaxTime = mLastPostTime;
			c.close();
		}

		SAXParserFactory spf = SAXParserFactory.newInstance();
		SAXParser sp = spf.newSAXParser();
		XMLReader xr = sp.getXMLReader();

		xr.setContentHandler(this);
		xr.setErrorHandler(this);

		URL url = new URL(mRSSURL);
		URLConnection connection = url.openConnection();
		connection.setConnectTimeout(10000);
		connection.setReadTimeout(10000);
		connection.connect();
		InputStream in = connection.getInputStream();
		parseRSS(in, GBKTable.isGBKUrl(mRSSURL));

		if (!isNewChannel) {
			if (DateUtils.getLongTime(mCurrentMaxTime) > DateUtils
					.getLongTime(mLastPostTime))
				mLastPostTime = mCurrentMaxTime;
		} else
			mLastPostTime = mCurrentMaxTime;

		Uri update = Uri.parse("content://" + SzRSSReader.AUTHORITY
				+ "/channels/" + mID);
		
		ContentValues values = new ContentValues();
		values.put(SzRSSReader.Channels.LAST_REFRESH_DATE, DateUtils
				.formatDate(Calendar.getInstance().getTime()));
		values.put(SzRSSReader.Channels.LAST_BUILD_DATE, mLastPostTime);

		mContent.update(update, values, null, null);
		return mID;
	}

	void parseRSS(InputStream is, boolean isGBKencoding) throws IOException,
			XmlPullParserException {

		XmlPullParser xpp = Xml.newPullParser();

		if (isGBKencoding) {
			StringBuffer sbuf = new StringBuffer();
			mGBKReader.read(is, sbuf);
			ByteArrayInputStream stream = new ByteArrayInputStream(sbuf
					.toString().getBytes());
			xpp.setInput(stream, null);
		} else {
			xpp.setInput(is, null);
		}
		int eventType;

		eventType = xpp.getEventType();
		while (eventType != XmlPullParser.END_DOCUMENT) {
			if (eventType == XmlPullParser.START_TAG) {
				Integer state = mStateMap.get(xpp.getName());

				if (state != null) {
					mState |= state.intValue();

					if (state.intValue() == STATE_IN_ITEM)
						mPostBuf = new ChannelPost();

					if (mID == -1 && xpp.getName().equals("title")
							&& (mState & STATE_IN_ITEM) == 0) {
						ContentValues values = new ContentValues();
						xpp.next();
						String title = xpp.getText();

						values.put(SzRSSReader.Channels.TITLE, title.trim());
						values.put(SzRSSReader.Channels.URL, mRSSURL);
						values.put(SzRSSReader.Channels.LAST_REFRESH_DATE, DateUtils
								.formatDate(Calendar.getInstance().getTime()));

						Uri added = mContent.insert(
								SzRSSReader.Channels.CONTENT_URI, values);
						Log.d("Channel Insert", "Uri added is: "
								+ added.toString());
						mID = Long.parseLong(added.getPathSegments().get(1));
					}

					if ((mState & STATE_IN_ITEM) == 0) {
						eventType = xpp.next();
						continue;
					}

					switch (mState) {
					case STATE_IN_ITEM | STATE_IN_ITEM_TITLE:
						if (!xpp.isEmptyElementTag()) {
							xpp.next();
							mPostBuf.title = xpp.getText().trim();
						}
						break;
					case STATE_IN_ITEM | STATE_IN_ITEM_DESC:
						if (!xpp.isEmptyElementTag()) {
							xpp.next();
							String text = xpp.getText();
							if (text != null)
								mPostBuf.desc = text.trim();
						}
						break;
					case STATE_IN_ITEM | STATE_IN_ITEM_LINK:
						if (!xpp.isEmptyElementTag()) {
							xpp.next();
							mPostBuf.link = xpp.getText().trim();
						}
						break;
					case STATE_IN_ITEM | STATE_IN_ITEM_DATE:
						if (!xpp.isEmptyElementTag()) {
							xpp.next();
							mPostBuf.setDate(xpp.getText());
						} else
							mPostBuf.setDate(null);
						break;
					case STATE_IN_ITEM | STATE_IN_ITEM_AUTHOR:
						if (!xpp.isEmptyElementTag()) {
							xpp.next();
							mPostBuf.author = xpp.getText().trim();
						}
						break;
					default:
						/* Don't care... */
					}

				}
			} else if (eventType == XmlPullParser.END_TAG) {
				Integer state = mStateMap.get(xpp.getName());

				if (state != null) {
					mState &= ~(state.intValue());

					if (state.intValue() == STATE_IN_ITEM) {
						if (mID == -1) {
							Log
									.d(TAG,
											"Oops, </item> found before feed title and our parser sucks too much to deal.");
							return;
						}

						String[] dupProj = new String[] { SzRSSReader.Posts._ID };

						Uri listURI = ContentUris.withAppendedId(
								SzRSSReader.Posts.CONTENT_URI_LIST, mID);

						Cursor dup = mContent.query(listURI, dupProj,
								"title = ? AND link = ?", new String[] {
										mPostBuf.title, mPostBuf.link }, null);

						try {
							long time = DateUtils.getLongTime(mPostBuf
									.getDate());

							if (dup.getCount() == 0
									&& (isNewChannel || time > DateUtils
											.getLongTime(mLastPostTime))) {
								if (mCurrentMaxTime == null
										|| time > DateUtils
												.getLongTime(mCurrentMaxTime)) {
									mCurrentMaxTime = mPostBuf.getDate();
								}

								ContentValues values = new ContentValues();

								values.put(SzRSSReader.Posts.CHANNEL_ID, mID);
								values.put(SzRSSReader.Posts.TITLE,
										mPostBuf.title);
								values.put(SzRSSReader.Posts.LINK, mPostBuf.link);
								//values.put(SzRSSReader.Posts.AUTHOR,mPostBuf.author);
								values.put(SzRSSReader.Posts.POST_DATE, mPostBuf
										.getDate());
								values.put(SzRSSReader.Posts.DESCRIPTION, mPostBuf.desc);

								Uri added = mContent.insert(
										SzRSSReader.Posts.CONTENT_URI, values);
								Log.d("ChannelRefresh", "Inserting new post: "
										+ added.toString());
							}
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						dup.close();
					}
				}
			}
			eventType = xpp.next();
		}
	}
	
	class MyByteArrayOutputStream extends ByteArrayOutputStream {
        public byte[] getTheBuf() {
            return buf;
        }
    }
	
	class GBKReader {
        byte buf[] = new byte[2048];
        char cbuf[] = new char[1024];
        public static final int ROW = GBKTable.END_COL - GBKTable.START_COL + 1;

        public void read(InputStream ins, StringBuffer sbuf) throws IOException {
            MyByteArrayOutputStream baos = new MyByteArrayOutputStream();
            int n;
            int total = 0;

            while ((n = ins.read(buf)) > 0) {
                baos.write(buf, 0, n);
                total += n;
            }
            byte buf[] = baos.getTheBuf();
            int len = baos.size();
            char table[] = GBKTable.data;

            int outlen = 0;
            int outidx = 0;
            int outmax = cbuf.length;
            int inidx;

            n = 0;
            try {
                for (inidx = 0; inidx < len; ) {
                    int b1 = buf[inidx++] & 0xff;
                    if (b1 < 0x81) {
                        if (b1 == '\r') {
                            b1 = '\n';
                        }
                        cbuf[outidx] = (char)b1;
                    } else {
                        int b2 = buf[inidx++] & 0xff;
                        if (b2 < 0x40) {
                            cbuf[outidx] = '?';
                        } else {
                            int i = (b1 - 0x81) * ROW + b2 - 0x40;
                            cbuf[outidx] = table[i];
                        }
                    }
                    outidx ++;
                    if (outidx >= outmax) {
                        sbuf.append(cbuf, 0, outidx);
                        outidx = 0;
                    }
                }
                sbuf.append(cbuf, 0, outidx);
                baos.close();
            } catch (ArrayIndexOutOfBoundsException e) {
                ;
            }
        }
    }
	
	private class ChannelPost {
		public String title;
		public Date date;
		public String desc;
		public String link;
		public String author;

		public ChannelPost() {
			/* Empty. */
			title = "";
			desc = "";
		}

		public void setDate(String str) {
			if (str == null || str.length() == 0) {
				date = new Date();
				return;
			}

			date = DateUtils.parseDate(str.trim());

			if (date == null)
				date = new Date();
		}

		public String getDate() {
			return DateUtils.formatDate(date);
		}
	}

}
